package com.rickpan.storage.multipart;

import com.rickpan.config.MinIOProperties;
import io.minio.*;
import io.minio.messages.Part;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * MinIO 多部分上传管理器
 * 管理 MinIO 的多部分上传会话和分片上传
 */
@Component
@ConditionalOnProperty(name = "app.storage.type", havingValue = "minio")
public class MinIOMultipartUploadManager {
    
    private static final Logger logger = LoggerFactory.getLogger(MinIOMultipartUploadManager.class);
    
    @Autowired
    private MinioClient minioClient;
    
    @Autowired
    private MinIOProperties minIOProperties;
    
    // 存储多部分上传会话信息
    private final Map<String, MultipartUploadSession> uploadSessions = new ConcurrentHashMap<>();
    
    /**
     * 多部分上传会话信息
     */
    public static class MultipartUploadSession {
        private final String uploadId;
        private final String objectKey;
        private final String minioUploadId;
        private final List<Part> parts;
        private final long createdTime;
        
        public MultipartUploadSession(String uploadId, String objectKey, String minioUploadId) {
            this.uploadId = uploadId;
            this.objectKey = objectKey;
            this.minioUploadId = minioUploadId;
            this.parts = new ArrayList<>();
            this.createdTime = System.currentTimeMillis();
        }
        
        // Getters
        public String getUploadId() { return uploadId; }
        public String getObjectKey() { return objectKey; }
        public String getMinioUploadId() { return minioUploadId; }
        public List<Part> getParts() { return parts; }
        public long getCreatedTime() { return createdTime; }
        
        public synchronized void addPart(Part part) {
            parts.add(part);
        }
        
        public synchronized List<Part> getPartsCopy() {
            return new ArrayList<>(parts);
        }
    }
    
    /**
     * 初始化多部分上传
     *
     * @param uploadId 上传会话ID
     * @param objectKey 对象键
     * @return 多部分上传会话
     * @throws Exception 初始化异常
     */
    public MultipartUploadSession initializeMultipartUpload(String uploadId, String objectKey) throws Exception {
        logger.debug("初始化MinIO多部分上传: uploadId={}, objectKey={}", uploadId, objectKey);

        try {
            // 暂时使用简化的实现，直接返回会话
            // TODO: 实现真正的 MinIO 多部分上传初始化
            String minioUploadId = "temp-" + uploadId + "-" + System.currentTimeMillis();

            // 创建会话
            MultipartUploadSession session = new MultipartUploadSession(uploadId, objectKey, minioUploadId);
            uploadSessions.put(uploadId, session);

            logger.info("MinIO多部分上传初始化成功: uploadId={}, minioUploadId={}", uploadId, minioUploadId);
            return session;

        } catch (Exception e) {
            logger.error("MinIO多部分上传初始化失败: uploadId={}", uploadId, e);
            throw new Exception("MinIO多部分上传初始化失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 上传分片
     * 
     * @param uploadId 上传会话ID
     * @param chunkIndex 分片索引（从0开始）
     * @param chunk 分片文件
     * @return 分片ETag
     * @throws Exception 上传异常
     */
    public String uploadPart(String uploadId, int chunkIndex, MultipartFile chunk) throws Exception {
        logger.debug("MinIO上传分片: uploadId={}, chunkIndex={}, 大小={}",
                    uploadId, chunkIndex, chunk.getSize());

        MultipartUploadSession session = uploadSessions.get(uploadId);
        if (session == null) {
            // 尝试恢复会话
            logger.warn("MinIO会话不存在，尝试恢复: uploadId={}", uploadId);
            session = recoverUploadSession(uploadId);
            if (session == null) {
                throw new IllegalStateException("多部分上传会话不存在且无法恢复: " + uploadId);
            }
        }
        
        try {
            // MinIO 分片编号从1开始
            int partNumber = chunkIndex + 1;

            // 暂时使用简化的实现，直接存储分片到临时位置
            // TODO: 实现真正的 MinIO 多部分上传分片
            String tempObjectKey = "temp-chunks/" + uploadId + "/chunk_" + chunkIndex;

            try (InputStream inputStream = chunk.getInputStream()) {
                minioClient.putObject(
                    PutObjectArgs.builder()
                        .bucket(minIOProperties.getBucketName())
                        .object(tempObjectKey)
                        .stream(inputStream, chunk.getSize(), -1)
                        .contentType("application/octet-stream")
                        .build()
                );

                String etag = "etag-" + partNumber + "-" + System.currentTimeMillis();

                // 创建分片信息
                Part part = new Part(partNumber, etag);
                session.addPart(part);

                logger.debug("MinIO分片上传成功: uploadId={}, partNumber={}, etag={}",
                           uploadId, partNumber, etag);

                return etag;
            }

        } catch (Exception e) {
            logger.error("MinIO分片上传失败: uploadId={}, chunkIndex={}", uploadId, chunkIndex, e);
            throw new Exception("MinIO分片上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 恢复上传会话
     */
    private MultipartUploadSession recoverUploadSession(String uploadId) {
        try {
            // 检查是否有已存在的临时分片文件来恢复会话
            String tempChunkPrefix = "temp-chunks/" + uploadId + "/";

            // 创建一个新的临时会话，使用默认对象键
            String objectKey = "uploads/" + uploadId + "/file";
            String minioUploadId = "recovered-" + uploadId + "-" + System.currentTimeMillis();

            MultipartUploadSession session = new MultipartUploadSession(uploadId, objectKey, minioUploadId);

            // 尝试恢复已上传的分片信息
            try {
                // 简化实现：假设分片按顺序上传，从0开始检查
                for (int i = 0; i < 1000; i++) { // 最多检查1000个分片
                    String tempObjectKey = tempChunkPrefix + "chunk_" + i;
                    try {
                        // 检查分片是否存在
                        minioClient.statObject(
                            StatObjectArgs.builder()
                                .bucket(minIOProperties.getBucketName())
                                .object(tempObjectKey)
                                .build()
                        );

                        // 如果存在，添加到会话中
                        String etag = "recovered-etag-" + (i + 1) + "-" + System.currentTimeMillis();
                        Part part = new Part(i + 1, etag);
                        session.addPart(part);

                    } catch (Exception e) {
                        // 分片不存在，停止检查
                        break;
                    }
                }

                logger.info("恢复MinIO上传会话成功: uploadId={}, 已恢复分片数: {}",
                           uploadId, session.getParts().size());

            } catch (Exception e) {
                logger.warn("恢复分片信息失败，创建空会话: uploadId={}", uploadId, e);
            }

            uploadSessions.put(uploadId, session);
            return session;

        } catch (Exception e) {
            logger.error("恢复MinIO上传会话失败: uploadId={}", uploadId, e);
            return null;
        }
    }

    /**
     * 完成多部分上传
     * 
     * @param uploadId 上传会话ID
     * @return 最终对象的ETag
     * @throws Exception 完成异常
     */
    public String completeMultipartUpload(String uploadId) throws Exception {
        logger.debug("完成MinIO多部分上传: uploadId={}", uploadId);

        MultipartUploadSession session = uploadSessions.get(uploadId);
        if (session == null) {
            // 尝试恢复会话
            logger.warn("MinIO会话不存在，尝试恢复: uploadId={}", uploadId);
            session = recoverUploadSession(uploadId);
            if (session == null) {
                throw new IllegalStateException("多部分上传会话不存在且无法恢复: " + uploadId);
            }
        }
        
        try {
            // 获取所有分片（按分片编号排序）
            List<Part> parts = session.getPartsCopy();
            parts.sort((p1, p2) -> Integer.compare(p1.partNumber(), p2.partNumber()));

            // 检查是否只有一个分片（小文件情况）
            if (parts.size() == 1) {
                // 小文件直接复制，不使用composeObject
                String tempObjectKey = "temp-chunks/" + uploadId + "/chunk_0";

                minioClient.copyObject(
                    CopyObjectArgs.builder()
                        .bucket(minIOProperties.getBucketName())
                        .object(session.getObjectKey())
                        .source(CopySource.builder()
                            .bucket(minIOProperties.getBucketName())
                            .object(tempObjectKey)
                            .build())
                        .build()
                );

                logger.info("小文件直接复制完成: uploadId={}, objectKey={}", uploadId, session.getObjectKey());
            } else {
                // 多分片文件使用 composeObject 合并
                java.util.List<ComposeSource> sources = new java.util.ArrayList<>();
                for (Part part : parts) {
                    String tempObjectKey = "temp-chunks/" + uploadId + "/chunk_" + (part.partNumber() - 1);
                    sources.add(ComposeSource.builder()
                        .bucket(minIOProperties.getBucketName())
                        .object(tempObjectKey)
                        .build());
                }

                // 合并分片到最终对象
                minioClient.composeObject(
                    ComposeObjectArgs.builder()
                        .bucket(minIOProperties.getBucketName())
                        .object(session.getObjectKey())
                        .sources(sources)
                        .build()
                );

                logger.info("多分片文件合并完成: uploadId={}, objectKey={}, 分片数: {}",
                           uploadId, session.getObjectKey(), parts.size());
            }

            String etag = "final-etag-" + System.currentTimeMillis();

            // 清理临时分片
            for (Part part : parts) {
                try {
                    String tempObjectKey = "temp-chunks/" + uploadId + "/chunk_" + (part.partNumber() - 1);
                    minioClient.removeObject(
                        RemoveObjectArgs.builder()
                            .bucket(minIOProperties.getBucketName())
                            .object(tempObjectKey)
                            .build()
                    );
                } catch (Exception e) {
                    logger.warn("清理临时分片失败: {}", part.partNumber(), e);
                }
            }

            // 清理会话
            uploadSessions.remove(uploadId);

            logger.info("MinIO多部分上传完成: uploadId={}, objectKey={}, etag={}",
                       uploadId, session.getObjectKey(), etag);

            return etag;

        } catch (Exception e) {
            logger.error("MinIO多部分上传完成失败: uploadId={}", uploadId, e);
            throw new Exception("MinIO多部分上传完成失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 中止多部分上传
     * 
     * @param uploadId 上传会话ID
     * @throws Exception 中止异常
     */
    public void abortMultipartUpload(String uploadId) throws Exception {
        logger.debug("中止MinIO多部分上传: uploadId={}", uploadId);
        
        MultipartUploadSession session = uploadSessions.get(uploadId);
        if (session == null) {
            logger.warn("多部分上传会话不存在，无法中止: {}", uploadId);
            return;
        }
        
        try {
            // 暂时使用简化的实现，清理临时分片
            // TODO: 实现真正的 MinIO 多部分上传中止
            List<Part> parts = session.getPartsCopy();
            for (Part part : parts) {
                try {
                    String tempObjectKey = "temp-chunks/" + uploadId + "/chunk_" + (part.partNumber() - 1);
                    minioClient.removeObject(
                        RemoveObjectArgs.builder()
                            .bucket(minIOProperties.getBucketName())
                            .object(tempObjectKey)
                            .build()
                    );
                } catch (Exception e) {
                    logger.warn("清理临时分片失败: {}", part.partNumber(), e);
                }
            }

            // 清理会话
            uploadSessions.remove(uploadId);

            logger.info("MinIO多部分上传已中止: uploadId={}", uploadId);

        } catch (Exception e) {
            logger.error("MinIO多部分上传中止失败: uploadId={}", uploadId, e);
            // 即使中止失败，也要清理本地会话
            uploadSessions.remove(uploadId);
            throw new Exception("MinIO多部分上传中止失败: " + e.getMessage(), e);
        }
    }
    
    /**
     * 获取上传会话信息
     * 
     * @param uploadId 上传会话ID
     * @return 会话信息，如果不存在返回null
     */
    public MultipartUploadSession getUploadSession(String uploadId) {
        return uploadSessions.get(uploadId);
    }
    
    /**
     * 清理过期的上传会话
     * 
     * @param maxAgeMillis 最大存活时间（毫秒）
     */
    public void cleanupExpiredSessions(long maxAgeMillis) {
        long currentTime = System.currentTimeMillis();
        List<String> expiredSessions = new ArrayList<>();
        
        for (Map.Entry<String, MultipartUploadSession> entry : uploadSessions.entrySet()) {
            if (currentTime - entry.getValue().getCreatedTime() > maxAgeMillis) {
                expiredSessions.add(entry.getKey());
            }
        }
        
        for (String uploadId : expiredSessions) {
            try {
                abortMultipartUpload(uploadId);
                logger.info("清理过期的多部分上传会话: {}", uploadId);
            } catch (Exception e) {
                logger.warn("清理过期会话失败: {}", uploadId, e);
            }
        }
    }
    
    /**
     * 获取当前活跃的上传会话数量
     */
    public int getActiveSessionCount() {
        return uploadSessions.size();
    }
}
